// Copyright (C) 2009 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using MmmDoc;
using MmmDoc.Database;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MmmDoc.Html
{
	/// <summary>Generates a table for each member in a type.</summary>
	internal sealed class GenerateTypeFile : WriteNormalHtml
	{
		public GenerateTypeFile(TypeDef type) : base(type.FileName() + "-file.html", "type-file.css")
		{
			m_type = type;
		}
		
		#region Protected Methods
		protected override string OnTitle
		{
			get
			{
				if (m_type.Namespace.Length > 0)
					return m_type.Namespace + '.' + m_type.Name + " Members";
				else
					return m_type.Name + " Members";
			}
		}
		
		protected override void OnWriteBodyContents(Writer writer)
		{
			DoWriteType(writer);
			
			// Constructors
			int i = 0;
			if (i < m_type.Members.Length && m_type.Members[i] is ConstructorMember)
			{
				if ((m_type.Members[i].Definition == null || !m_type.Members[i].Definition.Excluded) && !m_type.IsDelegate)
				{
					writer.WriteLine();
					writer.WriteElementLine("HR", null, "id", "constructors", "class", "divider");
					
					while (i < m_type.Members.Length && m_type.Members[i] is ConstructorMember)
					{
						DoWriteMember(writer, m_type.Members[i], i);
						++i;
					}
				}
				else
					++i;
			}
			
			DoWriteMembers(writer, i, MemberAccess.Public);
			DoWriteMembers(writer, i, MemberAccess.FamORAssem);
			DoWriteMembers(writer, i, MemberAccess.Family);
			DoWriteMembers(writer, i, MemberAccess.Assem);
			DoWriteMembers(writer, i, MemberAccess.FamANDAssem);
			DoWriteMembers(writer, i, MemberAccess.Private);
		}
		#endregion
		
		#region Private Methods
		private void DoWriteType(Writer writer)
		{
			writer.WriteStartTagLine("TABLE", "width", "100%", "class", "help");
				writer.WriteStartTag("CAPTION", "class", "help");
					DoWriteTypeHeader(writer);
				writer.WriteEndTag("CAPTION");
				writer.WriteLine();
				
				writer.WriteStartTagLine("COLGROUP");
					writer.WriteElementLine("COL", null, "class", "help");
				writer.WriteEndTagLine("COLGROUP");
				
				writer.WriteStartTagLine("TBODY");
					DoWriteTypeHelp(writer);
				writer.WriteEndTagLine("TBODY");
			writer.WriteEndTagLine("TABLE");
		}
		
		private void DoWriteMembers(Writer writer, int startIndex, MemberAccess access)
		{
			bool first = true;
			for (int i = startIndex; i < m_type.Members.Length; ++i)
			{
				Member member = m_type.Members[i];
				if ((member.Definition == null || !member.Definition.Excluded) && member.Access == access)
				{
					if (!m_type.IsEnum || member.Name != "value__")
					{
						if (first)
						{
							writer.WriteLine();
							writer.WriteElementLine("HR", null, "id", access + "_Members", "class", "divider");
							first = false;
						}
						else
							writer.WriteLine();
						
						DoWriteMember(writer, member, i);
					}
				}
			}
		}
		
		private void DoWriteMember(Writer writer, Member member, int i)
		{
			if (Settings.WarnIfMissing && member.Definition == null)
				Console.Error.WriteLine("{0} is missing documentation.", member.FullName);
			
			writer.WriteStartTagLine("TABLE", "width", "100%", "class", "help");
				writer.WriteStartTag("CAPTION", "id", "M" + i, "class", "help");
					WriteSignature.Invoke(writer, member, true); 
				writer.WriteEndTag("CAPTION");
				writer.WriteLine();
				
				writer.WriteStartTagLine("COLGROUP");
					writer.WriteElementLine("COL", null, "class", "help");
				writer.WriteEndTagLine("COLGROUP");
				
				writer.WriteStartTagLine("TBODY");
					DoWriteMemberHelp(writer, member);
				writer.WriteEndTagLine("TBODY");
			writer.WriteEndTagLine("TABLE");
		}
		
		private void DoWriteTypeHeader(Writer writer)
		{
			if (m_type.IsInterface)
				writer.Write("interface ");
			else if (m_type.IsEnum)
				writer.Write("enum ");
			else if (m_type.IsDelegate)
				writer.Write("delegate ");
			else if (m_type.IsStruct)
				writer.Write("struct ");
			else
				writer.Write("class ");
			writer.Write(m_type.Name);
			DoWriteTypeGenericArgs(writer);
		}
		
		private void DoWriteTypeHelp(Writer writer)
		{
			DoWriteTypeSignatureRow(writer);
			if (m_type.Definition != null)
			{
				DoWriteHelpRow(writer, null, "summary", m_type.Definition.Summary);
				DoWriteHelpRow(writer, "Type Parameters", "type-params", m_type.Definition.TypeParams);
//				DoWriteHelpRow(writer, "Value", "value", m_type.Definition.Value);
//				DoWriteHelpRow(writer, "Parameters", "params", m_type.Definition.Params);
//				DoWriteHelpRow(writer, "Return Value", "returns", m_type.Definition.Returns);
				DoWriteHelpRow(writer, "Permissions", "permissions", m_type.Definition.Permissions);
				DoWriteHelpRow(writer, "Exceptions", "exceptions", m_type.Definition.Exceptions);
				DoWriteHelpRow(writer, "Remarks", "remarks", m_type.Definition.Remarks);
				DoWriteHelpRow(writer, "Example", "examples", m_type.Definition.Examples);
				DoWriteHelpRow(writer, "See Also", "see-also", m_type.Definition.SeeAlso);
		}
		}
		
		private void DoWriteMemberHelp(Writer writer, Member member)
		{
			DoWriteMemberSignatureRow(writer, member);
			
			if (member.Definition != null)
			{
				DoWriteHelpRow(writer, null, "summary", member.Definition.Summary);
				DoWriteHelpRow(writer, "Type Parameters", "type-params", member.Definition.TypeParams);
				DoWriteHelpRow(writer, "Value", "value", member.Definition.Value);
				DoWriteHelpRow(writer, "Parameters", "params", member.Definition.Params);
				DoWriteHelpRow(writer, "Return Value", "returns", member.Definition.Returns);
				DoWriteHelpRow(writer, "Permissions", "permissions", member.Definition.Permissions);
				DoWriteHelpRow(writer, "Exceptions", "exceptions", member.Definition.Exceptions);
				DoWriteHelpRow(writer, "Remarks", "remarks", member.Definition.Remarks);
				DoWriteHelpRow(writer, "Example", "examples", member.Definition.Examples);
				DoWriteHelpRow(writer, "See Also", "see-also", member.Definition.SeeAlso);
			}
		}
		
		private const string NonBreakingWidthSpace = "\xA0";
		
		private void DoWriteHelpRow(Writer writer, string label, string klass, List<LabeledMarkup<string>> contents)
		{
			if (contents.Count > 0)
			{
				writer.WriteStartTagLine("TR", "class", "help");
				writer.WriteStartTagLine("TD", "class", klass);
				writer.WriteElement("P", null, "class", "help-section");
				
				if (label != null)
				{
					writer.WriteStartTag("SPAN", "class", "label");
					writer.Write(label);
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
				}
				
				for (int i = 0; i < contents.Count; ++i)
				{
					LabeledMarkup<string> markup = contents[i];
					Contract.Assert(!markup.Label.Contains(':'), markup.Label + " has a colon");
					
					writer.WriteStartTag("SPAN", "class", "sublabel");
					writer.Write(markup.Label);
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
					
					writer.Write(new string(NonBreakingWidthSpace[0], 4));
					WriteMarkup.Invoke(writer, markup.Markup, false, m_type);
					
					if (i + 1 < contents.Count)
						writer.WriteElement("BR", null);
				}
				
				writer.WriteLine();
				writer.WriteEndTagLine("TD");
				writer.WriteEndTagLine("TR");
			}
		}
		
		private void DoWriteHelpRow(Writer writer, string label, string klass, List<LabeledMarkup<CodeReference>> contents)
		{
			if (contents.Count > 0)
			{
				writer.WriteStartTagLine("TR", "class", "help");
				writer.WriteStartTagLine("TD", "class", klass);
				writer.WriteElement("P", null, "class", "help-section");
				
				if (label != null)
				{
					writer.WriteStartTag("SPAN", "class", "label");
					writer.Write(label);
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
				}
				
				for (int i = 0; i < contents.Count; ++i)
				{
					LabeledMarkup<CodeReference> markup = contents[i];
					Contract.Assert(!markup.Label.XmlName.Contains(':'), markup.Label.XmlName + " has a colon");
					
					writer.WriteStartTag("SPAN", "class", "sublabel");
					WriteCodeReference.Write(writer, markup.Label, null, m_type);
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
					
					writer.Write(new string(NonBreakingWidthSpace[0], 4));
					WriteMarkup.Invoke(writer, markup.Markup, false, m_type);
					
					if (i + 1 < contents.Count)
						writer.WriteElement("BR", null);
				}
				
				writer.WriteLine();
				writer.WriteEndTagLine("TD");
				writer.WriteEndTagLine("TR");
			}
		}
		
		private void DoWriteHelpRow(Writer writer, string label, string klass, List<CodeReference> contents)
		{
			if (contents.Count > 0)
			{
				writer.WriteStartTagLine("TR", "class", "help");
				writer.WriteStartTagLine("TD", "class", klass);
				writer.WriteElement("P", null, "class", "help-section");
				
				if (label != null)
				{
					writer.WriteStartTag("SPAN", "class", "label");
					writer.Write(label);
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
				}
				
				for (int i = 0; i < contents.Count; ++i)
				{
					WriteCodeReference.Write(writer, contents[i], null, m_type);
					writer.WriteElement("BR", null);
				}
				
				writer.WriteLine();
				writer.WriteEndTagLine("TD");
				writer.WriteEndTagLine("TR");
			}
		}
		
		private void DoWriteHelpRow(Writer writer, string label, string klass, List<Markup> contents)
		{
			if (contents.Count > 0)
			{
				for (int i = 0; i < contents.Count; ++i)
				{
					writer.WriteStartTagLine("TR", "class", "help");
					writer.WriteStartTagLine("TD", "class", klass);
					writer.WriteElement("P", null, "class", "help-section");
					
					writer.WriteStartTag("SPAN", "class", "label");
					writer.Write(contents.Count == 1 ? label : (label + ' ' + (i + 1)));
					writer.WriteEndTag("SPAN");
					writer.WriteElement("BR", null);
					
					WriteMarkup.Invoke(writer, contents[i], false, m_type);
					
					writer.WriteLine();
					writer.WriteEndTagLine("TD");
					writer.WriteEndTagLine("TR");
				}
			}
		}
		
		private void DoWriteHelpRow(Writer writer, string label, string klass, Markup contents)
		{
			if (contents != null)
			{
				writer.WriteStartTagLine("TR", "class", "help");
				writer.WriteStartTagLine("TD", "class", klass);
				writer.WriteElement("P", null, "class", "help-section");
				
				if (label != null)
				{
					writer.WriteStartTag("SPAN", "class", "label");
					writer.Write(label);
					writer.WriteEndTag("SPAN");
					
					writer.WriteElement("BR", null);
				}
				
				WriteMarkup.Invoke(writer, contents, false, m_type);
				
				writer.WriteLine();
				writer.WriteEndTagLine("TD");
				writer.WriteEndTagLine("TR");
			}
		}
		
		private void DoWriteTypeGenericArgs(Writer writer)
		{
			if (m_type.GenericArgs != null)
			{
				writer.Write("<");
				for (int i = 0; i < m_type.GenericArgs.Length; ++i)
				{
					WriteType.Invoke(writer, m_type.GenericArgs[i], false);
					
					if (i + 1 < m_type.GenericArgs.Length)
						writer.Write(", ");
				}
				writer.Write(">");
			}
		}
		
		private void DoWriteTypeSignatureRow(Writer writer)
		{
			writer.WriteStartTagLine("TR");
			writer.WriteStartTagLine("TD", "class", "type-sig");
			writer.WriteElement("P", null, "class", "help-section");
			
			foreach (string attr in m_type.CustomAttributes)
			{
				writer.WriteElementLine("SPAN", attr, "class", "attribute");
				writer.WriteElementLine("BR", null);
			}
			
			writer.WriteElement("SPAN", m_type.Attributes, "class", "keyword");
			writer.Write(" ");
			writer.WriteElement("SPAN", m_type.Name, "class", "type-sig-type");
			DoWriteTypeGenericArgs(writer);
			if (m_type.IsEnum)
			{
				FieldMember value = (FieldMember) m_type.Members.Single(m => m.Name == "value__");
				if (value.Type.FullName != "System.Int32")
				{
					writer.Write(" : ");
					WriteType.Invoke(writer, value.Type, false);
				}
			}
			else if (m_type.BaseTypes.Length > 0)
			{
				writer.Write(" : ");
				for (int i = 0; i < m_type.BaseTypes.Length; ++i)
				{
					WriteType.Invoke(writer, m_type.BaseTypes[i], false);
					
					if (i + 1 < m_type.BaseTypes.Length)
						writer.Write(", ");
				}
			}
			
			writer.WriteLine();
			writer.WriteEndTagLine("TD");
			writer.WriteEndTagLine("TR");
		}
		
		private void DoWriteMemberSignatureRow(Writer writer, Member member)
		{
			writer.WriteStartTagLine("TR");
			writer.WriteStartTagLine("TD", "class", "member-sig");
			writer.WriteElement("P", null, "class", "help-section");
			
			foreach (string attr in member.CustomAttributes)
			{
				writer.WriteElement("SPAN", attr, "class", "attribute");
				writer.WriteElementLine("BR", null);
			}
			
			if (m_type.IsEnum)
			{
				FieldMember field = (FieldMember) member;
				writer.WriteElement("SPAN", field.Name, "class", "member-name");
				writer.Write(" = ");
				writer.WriteElement("SPAN", string.Format("{0:X}", field.Value), "class", "number");
			}
			else
				WriteSignature.Invoke(writer, member, false);
			
			writer.WriteLine();
			writer.WriteEndTagLine("TD");
			writer.WriteEndTagLine("TR");
		}
		#endregion
		
		#region Fields
		private TypeDef m_type;
		#endregion
	}
}
